-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
completely replace underscore with lodash #315
Conversation
…set of underscore (full api compatibility on stable versions)
👍 from me... I always use lodash over underscore now! |
👍 |
Please be aware that there are few differences between them, first and biggest of which is their behavior when it comes to inherited properties of objects given to |
I've been sticking to underscore over lodash because they're more strict about only including features that can't be misinterpreted (e.g. the limitations on _.extend) as a language addition, whereas lodash has more features but seems more likely to do something unexpected or non-standard. It's a bit hard to explain what I mean by that - generally underscore seems more inline with how features (_.each, _.map, etc) are eventually implemented in Javascript. I'm happy to go with community consensus on this and switch. @kessler could you be a bit more specific though with your "far superior in many ways" comment? The speed improvement seems like the biggest argument (given that common niceties we need are also added to the
bound call with arguments: bound and partially applied call with arguments:
Seems like the results lean slightly in favour of lodash, not the comprehensive 1.4x improvement when you look at the overall benchmark results though (disclaimer: I didn't use a calculator) Are there particular lodash functions that anyone has been looking to use in keystone development that would make a definitive argument to switch? Finally, it may also be worth switching to use of native methods as a rule for functionality available in the V8 implementation of Javascript (e.g. |
@JedWatson, I've got your back! Screw community consensus. Doing the simpler thing is more important! |
Hi, Give me several days, I will conduct a more thorough "survey" and will come back with specifics. |
Firstly, I ran all these benchmarks from http://lodash.com/benchmarks using Lo-Dash(modern) and Underscore (production), on my machine (i3 4gigs mem, windows 7, chrome 34) and came back with extremely different results. I never expected such a huge difference, its troubling. Here is a side by side comparison:
lodash.min x 310,267 ops/sec ±8.47% (38 runs sampled)
bound call with arguments:
bound and partially applied call with arguments:
Maybe its worth involving the creator of lodash @jdalton in this discussion. |
Is this even a problem in Keystone that Underscore or Lodash is or isn't fast enough? |
@moll For your convenience I've included a regex search for "_." in keystone source files:
If you ignore some of these, like public/js and docs, its still, in my opinion more than "figurative 5 places". Don't let the count confuse you, some of these sites are called in loops many times over and some sit at the very base of the framework. |
Lo-Dash aligns with ES6 A big reason I created Lo-Dash was to address cross-environment consistency issues that Underscore refused to tackle. To further reduce the unexpected, Lo-Dash also follows semver, so unlike Underscore we don't introduce back-compat breaking changes with each minor release.
It comes down to consistency, support, features, customization, & performance. Lo-Dash goes above and beyond to ensure consistent behavior of its methods in all supported environments. We've listened to the community and added features like a deep clone and merge, that time after time have been punted on by Underscore. Lo-Dash also gives you more ways to consume the utility methods even as bundles of AMD modules, Node modules, or individual npm packages.
Performance is great and there are lots-of-examples of devs seeing performance improvements by switching, but performance in Lo-Dash has always taken a back seat to consistency and correctness. I used performance to offset the cost of fixes/consistency and then took a step back and realized it went a bit further ;D
Actually native
Both Lo-Dash and Underscore stray from the spec'ed native methods in ways, usually to be more usable or add popular features. The difference is Lo-Dash is consistent where Underscore is not. For example Underscore will defer to native if it exists but fallback to its own implementation if it doesn't. The problem is its fallbacks don't align with native so depending on your environment the methods will act differently and produce different results. Underscore is taking steps to address this in their next bump by aligning closer to Lo-Dash. Underscore has started aligning its API to Lo-Dash. For example they added chainable |
The gotcha about And I would bet a few of my cookies that most authors are fully ignorant of this tradeoff. |
Anyone wanna bet that there's a security problem lurking in some app or library purely from the behavior of Lodash's |
It does have some Underscore-compat side effects but they are minimal and have been worked around with methods like The methods you listed,
You can always use the lodash.underscore build to ease in to the switch.
I get that which is why we follow the language precedents and offer things like |
Speaking of QA, Underscore doesn't even run its unit tests in Node ( |
Ah, shit, you're right @ Well, yeah, var opts = Object.create(myDefaults)
opts.foo = 42
yourFunction(opts)
function yourFunction(opts) {
opts = _.extend({}, yourDefaults, opts)
if (opts.foo != null) somethingOrOther()
} Remove the yourDefaults line and you've got the code running The thing is, if you find inherited props to be a core part as well, then you should see that Lodash's behavior is actively harmful for the use of inherited properties beyond classes. The worse part, and that's not entirely your fault (:-)), is that the support or non-support of inherited properties will become a hidden API detail — an implementation detail leaking to the public API. |
Lo-Dash's behavior aligns with the language and has been a non-issue IRL. If you want inheritance, flattening it onto an object isn't the way to go about it (that's not inheritance). You should use
Keeping a clear documented distinction and aligning with language precedents is the way to go. Being ambiguous is the real danger which leads to real API issues (as I mentioned previously). |
Would you mind clarifying which parts of the language do you mean with aligning?
Well, I'd say inheritance must be an implementation detail to the receiver — isn't that how inheritance and polymorphism are supposed to work? In the example, Also, think of getters: You have an object with a getter set in the prototype that will return something. Pass that object to |
Yes,
I'd say that a object iteration, via a for-in loop, is part of it too. We provide
Ya well, that's like your opinion man, (big lebowski). The common case is iterating over own properties. This allows optimizations like using
That's right and I pushed Underscore to fix that in their implementation of methods like
Getters/Setters are out of scope for both Lo-Dash and Underscore as they can't be addressed consistently across environments. For example neither handle getters/setters in |
What is the use case that supposedly turns iterating over own properties to the common case? People are
I'm sure you noticed, but Don't get me wrong, my arguments are not only in regards to
Yet Lodash doesn't provide a function to extend objects with inherited properties or do any of the other useful things it does.
Oh, I didn't mean that. Especially because the use of getters must be an implementation detail that must not change behavior. I meant if I give you an object (an options object, e.g.) with a getter your function should behave equivalent regardless if the getter was in the prototype or as an own property. Lack of setters is not a problem, as we just agreed that passed-in objects should never be changed. |
It's the common case in both Underscore and Lo-Dash.
I'm sure devs do use Lo-Dash for OOP. We've fixed object iteration bugs to aid in that.
Sure it's pseudo private, but it's being used by Node to do things like applying options objects which makes it very relevant to this discussion.
Yap, jQuery doesn't currently align with
Lo-Dash and Underscore are about providing the small bits devs can use to create other things. So Lo-Dash offers
Getters can have side effects too, neither libs support them. You like Underscore? Great, use the lodash.underscore.js build. You'll get drop-in Underscore compatibility plus a bit of perf & consistency to boot. |
Thanks both @jdalton and @moll for some really insightful discussion - when I pushed back on the simplicity of this PR's original description for more detailed reasoning, let's just say this response really blows my expectations out of the water. I also really appreciate your weigh-in, @jdalton, it's great to have the differences, background and reasoning explained. I'm now very comfortable to switch to Lo-Dash for keystone and keystone-utils, and appreciate the benefits. As @moll has explained it's not a perfect environment, there are a number of inconsistencies and in this case we've got the advantage that this decision is just for keystone itself (and will only be run in node) and project authors can still use whichever library they prefer. Regarding the difference in behaviours of Because jQuery came up it might be worth mentioning that I generally use jQuery and
This is really good to know, and a bit surprising. I'll continue preferring |
@kessler would you mind rebasing this PR so I can merge it without conflicts? looks like there's been a change github can't automatically merge. otherwise I'll come back to it when I've wrapped up some other changes I'm working on. |
@JedWatson no probs, will do. |
You're right. I've submitted a bug to Node.js for that. ;-) Indeed, thanks @jdalton for the discussion! I would still argue that Lodash's design and behavior around inherited properties is to the detriment of the whole language and simplicity by adding to the inconsistency of APIs, but hey, we can find another place to continue or just create our own Underscores. ;-)
Unfortunately that's the place where it matters. If you use
In a controlled environment like Node.js on V8 I doubt that's going to be an issue. Keeping 3rd party dependencies low has its own benefit and the Node stdlib itself seems to find plain JavaScript totally suitable as it doesn't use |
You could also use the |
I'm sorry, I don't agree. Your doomsday stance is totally unfounded and a bit over the top. I've shown that your concern is not a real world issue. Lo-Dash has more than 2 million npm downloads a month and more than 3,000 dependents on npm alone. No one is hung up on this
Again, more of the same.
Node may not use
You could also do
The Underscore compat build is not equiv to Lo-Dash. In order to be a drop-in replacement all of Lo-Dash's features are removed. Using the Underscore compat build is a good first step but I'd suggest trying the full Lo-Dash first to see if there's even an issue. |
Oh, absolutely, things are not going to blow up all at once. It's a gradual decline as with any complexity: things go under the radar until it's a big ball of mud, and by then, everybody's turned to pigs and love rolling around. I wish I could join, but I've got these new blue suede shoes I'd love to keep clean. :) Popularity, however, should never be confused with correctness or simplicity. API design has many parallels with the human-computer interaction field and you don't see anyone there suggesting waiting until >50% of the people complain to do anything about it.
Oh, though when I checked out |
Again, I don't agree.
Should I take that as a personal attack or an attack against Lo-Dash? 😀
Popularity helps put into context the issue and gives us the reach to be exposed to issues big and small. If it was a pressing problem we would have addressed it.
Yah, you read that wrong. The @moll I think you've derailed the thread enough. |
What do you have against pigs? They're so cute! 🐷
I'd say our discussion was rather relevant to the whole Underscore vs Lodash topic, but I'll see you at the Node thread! |
@moll - that after all this fuss, will you help me make the transition of keystone to lodash a safe one? |
@kessler: How may I do so? A side question — is @JedWatson the benevolent dictator in Keystone's case or who's the final decision maker? |
@moll obviously you have a much deeper understanding than me about both keystone and the fine prints of inherited properties, so it would be very helpful if you can simply go over the diff. As for the dictatorship status I have no clue, but hopefully @JedWatson could shed some light over this. |
Oh, thanks, I'm flattered, but unfortunately I do not have enough knowledge of Keystone's internals to know which public APIs will be affected by the inherited-options issue. I'm confident @JedWatson will be able to tell in a heartbeat. You could also use, like John-David suggested, the Whichever way @JedWatson decides to go, I would support consistency and clear documentation. If inherited properties are to be disallowed, they should be ignored in every API and documented clearly in the API docs. |
I've been looking into it further and think the best option is to delay this for a little bit. There are quite a few changes going on in I would like to switch to LoDash in the near future, for performance and other benefits, but for consistency we should switch the core I'd also like to (a) make it an isolated change that we can test for a bit, and (b) have some more automated tests in place first so we can make the switch with better confidence. Thanks to @kessler for bringing this up in the first place, and to both @jdalton and @moll for your input and caring so much about the issues involved. I really appreciate it. Oh, and Keystone's leadership hasn't come up before but yes, it would be me. |
lodash is a suprioer superset of underscore (full api compatibility on stable versions) and is far superior in many ways.